package nl.hu.lcv.agents;

import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import nl.hu.lcv.blackboard.client.BlackboardClientUtils;
import nl.hu.lcv.blackboard.client.BlackboardMonitor;
import nl.hu.lcv.blackboard.data.PostItProtos.PostIt;
import nl.hu.lcv.blackboard.data.PostItProtos.PostItBox;
import nl.hu.lcv.scheduling.Utils;

import org.apache.xmlbeans.XmlException;
import xHUAgileManufacturingAFDLSteps.PartDocument.Part;
import xHUAgileManufacturingAFDLSteps.PropertyDocument.Property;
import xHUAgileManufacturingAFDLSteps.StepDocument;

public class ProjectAgent extends Agent{

	private HashMap<String, String> Mysteps = null;
	ArrayList<String> myEquiplets = null;
	private ArrayList<Product> myProducts = null;
	private CurrentAvailabilityDatabase currDb = null;
	private long currentTimeStep;
	private KillBehaviour kb;
	private BlackboardMonitor bb2Monitor = null;
	
	private class KillBehaviour extends TickerBehaviour
	{
		
		public KillBehaviour(Agent a, long timeout) {
			super(a, timeout);	
			
			
		}

		@Override
		protected void onTick() {
			myAgent.doDelete();
			
		}	
		
	}
		
	private class ReceiveCurrentTimeStepBehaviour extends CyclicBehaviour
	{		 
		@Override
		public void action() 
		{
			MessageTemplate mt = MessageTemplate.and(MessageTemplate.MatchPerformative(ACLMessage.INFORM), MessageTemplate.MatchOntology("Boef-ontology"));
			try {
				ACLMessage msg = myAgent.receive(mt);
				if(msg != null)
				{					
						currentTimeStep = (Long)msg.getContentObject();
						System.out.println(currentTimeStep);										
				}
			}
			catch (UnreadableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
			}						
			block();
		}
	}
	
	
	private class LookingForWorkBehaviour extends CyclicBehaviour
	{
		public void action() 
		{				
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
			ACLMessage msg = myAgent.receive(mt);
			if(msg != null)
			{
				
			    try {
				
			    	//the productsteps from the new desired product, should get them from BB1 but was lasy.
			    	String[] arr = (String[])msg.getContentObject();
			    	boolean possibleToMakeProduct = false;
			    	for(String step : arr)
			    	{
			    		for(String eq : myEquiplets)
			    		{
			    			if(currDb.hasThisStep(eq, step))
			    			{
			    				possibleToMakeProduct = true;
			    				break;
			    				
			    			}	
			    			possibleToMakeProduct = false;			    			
			    		}			    	
			    		if(!possibleToMakeProduct)
			    			break;
			    	}
				
			    	if(possibleToMakeProduct)
			    	{			    		
						ACLMessage reply = new ACLMessage(ACLMessage.PROPOSE);
				    	reply.setInReplyTo(msg.getReplyWith());
						reply.setConversationId(msg.getConversationId());
						//System.out.println("I refuse");
						reply.addReceiver(msg.getSender());
						send(reply);
			    	}
			    	else
			    	{
				   		//TODO 
						//always refuse for the moment
						ACLMessage reply = new ACLMessage(ACLMessage.REFUSE);
				    	reply.setInReplyTo(msg.getReplyWith());
						reply.setConversationId(msg.getConversationId());
						//System.out.println("I refuse");
						reply.addReceiver(msg.getSender());
						send(reply);	
			    	}
							    
			    } catch (UnreadableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
				
			}			
			else
				block();
		}
	}
	
	private class WorkConfirmBehaviour extends CyclicBehaviour
	{
		@Override
		public void action() 
		{
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL);
			ACLMessage msg = myAgent.receive(mt);				
			if(msg != null)
			{					
				try {
					addBehaviour(new ScheduleNewProductBehaviour(msg.getContentObject()));
				} catch (UnreadableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
					
			}
			else
				block();		
		}	
	}
	
	
	private class ScheduleNewProductBehaviour extends Behaviour
	{
		private Product product;
		private int step = 0;
		PostItBox readFromBbBox = null;
		public ScheduleNewProductBehaviour(Object product) {
			this.product = (Product)product;
			myProducts.add((Product)product);
			kb.reset();
		}
		
		@Override
		public void action() {
			
			switch(step)
			{
			case (0):				
				readFromBbBox = PostItBox.newBuilder()
				.setIsWrite(false)
				.setReadOwner(product.getName())
				.setZone("BB1")
				.setCurrentTimeStep(currentTimeStep)
				.setFilter(PostItBox.Filter.newBuilder().setFiltername("PostItFilter"))
				.build();
				readFromBbBox = BlackboardClientUtils.readFromBlackboard(readFromBbBox);
				System.out.println("HI " + readFromBbBox.getPostItsCount());
				System.out.println(product.getName()+ " is read from BB1.");
				if(Mysteps == null)
				{
					
					Mysteps = new HashMap<String, String>();
					myEquiplets = new ArrayList<String>();
					for(PostIt p : readFromBbBox.getPostItsList())
					{								//TODO: Should check for in xml for step
						String step = null;
						try {
							step = StepDocument.Factory.parse(p.getPayload()).getStep().getAction().getVerb();
						} catch (XmlException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						if(!Mysteps.containsKey(step))						
						{							
							String equiplet = currDb.GetEquiplet(step);
							if(equiplet != null)
							{
							ArrayList<String> arr = currDb.getEquipletSteps(equiplet);
							for(String i : arr)
								Mysteps.put(i, equiplet);	
							//System.out.println(equiplet);							
							if(!myEquiplets.contains(equiplet))
								myEquiplets.add(equiplet);
						
							}
						}
					}
					
				}
				step++;
			break;	
			case (1):				
				//Scheduling should come here
				//System.out.println("Scheduling product:" + product.getName());
				if(bb2Monitor.getKey())
				{
					product.setDeadline((int)currentTimeStep + product.getRange() + 1);				
					HashMap<String,ArrayList<Long>> availableSteps = new HashMap<String,ArrayList<Long>>();
					//System.out.println(myEquiplets.size());
					for(String equiplet : myEquiplets)
					{
					    availableSteps.put(equiplet, Utils.GetAvailability(myAgent.getLocalName(), equiplet, currentTimeStep, product.getDeadline()));
					}				
					ArrayList<PostIt> arr = new ArrayList<PostIt>();					
					if(Utils.firstFit(arr, product, currentTimeStep, readFromBbBox, Mysteps, availableSteps))
					{						
						PostItBox writeToBbBox = PostItBox.newBuilder()
								.setIsWrite(true)
								.setZone("BB2")
								.addAllPostIts(arr)
								.build();
						BlackboardClientUtils.writeToBlackboard(writeToBbBox);		
						step = step+2;
						System.out.println("Scheduling succesfull, writing to BB2");
						bb2Monitor.giveKeyBack();
					}
					else
					{
						step++;
					}
					
				}
				break;
			case(2):
				//reschedule here				
				System.out.println("Reschedule madness");
				ArrayList<PostIt> backupPostIts = null;
				ArrayList<Product> productsToReschedule = new ArrayList<Product>();
				ArrayList<String> productsNamesToReschedule = new ArrayList<String>();
										
				for(Product p : myProducts)
				{
					if(p.getDeadline() > product.getDeadline())
					{
						productsToReschedule.add(p);
						productsNamesToReschedule.add(p.getName());
					}
				}					
				Collections.sort(myProducts);		
				
				backupPostIts = Utils.getCurrentScheduledSteps(getLocalName(), productsNamesToReschedule);
					
				HashMap<String,ArrayList<Long>> availableSteps = new HashMap<String,ArrayList<Long>>();
				for(String equiplet : myEquiplets)
				{
				    availableSteps.put(equiplet, Utils.GetAvailability(myAgent.getLocalName(), equiplet, currentTimeStep, product.getDeadline()));
				}
				
				boolean schedulingOk = false;
				ArrayList<PostIt> arr = new ArrayList<PostIt>();
				if(Utils.firstFit(arr, product, currentTimeStep, readFromBbBox, Mysteps, availableSteps))
				{
					if(Utils.reschedule(arr, currentTimeStep, Mysteps, availableSteps, productsToReschedule))
					{
						schedulingOk = true;
						for(PostIt pp : arr)						
							System.out.println("Product: "+ pp.getProductRef() + " Scheduled on equiplet: " + pp.getOwner() +" step: " + pp.getPayload() +" on timestep: " + pp.getTimestep());
					}									
				}
				
				PostItBox writeToBbBox = PostItBox.newBuilder()
						.setIsWrite(true)
						.setZone("BB2")
						.build();
				if(schedulingOk)
				{									
					writeToBbBox = PostItBox.newBuilder(writeToBbBox).addAllPostIts(arr).build();
					BlackboardClientUtils.writeToBlackboard(writeToBbBox);		
					System.out.println("REScheduling succesfull, writing to BB2");
				}
				else
				{									
					writeToBbBox = PostItBox.newBuilder(writeToBbBox).addAllPostIts(backupPostIts).build();
					BlackboardClientUtils.writeToBlackboard(writeToBbBox);		
					System.out.println("REScheduling unsuccesfull, WRITING BACKUP BACK22");									
				}						
				bb2Monitor.giveKeyBack();	
				step++;
				break;			
			}					
		}

		@Override
		public boolean done() {
			
			return step == 3;
		}	
	}
	
	public void setup()
	{
		
		this.myProducts = new ArrayList<Product>();
		this.currentTimeStep = (Long)getArguments()[2];
		this.bb2Monitor = (BlackboardMonitor)getArguments()[3];
		System.out.println("ProjectAgent startup");
		try {
	    	DFAgentDescription dfd = new DFAgentDescription(); 
	 	    dfd.setName(getAID()); 
	 	    ServiceDescription sd = new ServiceDescription(); 
	 	    sd.setType("Project");
	 	    sd.setName("Project-agent");
	 	   ServiceDescription sr = new ServiceDescription(); 
		    sr.setType("TimeReceiver"); 
		    sr.setName("TimeSync");
	 	    
		    dfd.addServices(sr);
	        dfd.addServices(sd); 
	 	    DFService.register(this, dfd); 
 	    } 
 	    catch (FIPAException fe) { 
 	      fe.printStackTrace();
 	    }		
		kb = new KillBehaviour(this, 20000); 	
		addBehaviour(kb);
 	    addBehaviour(new LookingForWorkBehaviour());
 	    addBehaviour(new WorkConfirmBehaviour());
 	    addBehaviour(new ReceiveCurrentTimeStepBehaviour());
		addBehaviour(new ScheduleNewProductBehaviour(getArguments()[0]));
		this.currDb = (CurrentAvailabilityDatabase) getArguments()[1];
	}
	
	public void takeDown()
	{
		try 
		 { 
		      DFService.deregister(this); 
		 } 
		 catch (FIPAException fe) 
		 { 
		      fe.printStackTrace(); 
		 } 	 
		
	}
	
}
